// CyTargetLinker RIN conversion,
//
// scripts to create RIN network files for several online databases
// can be used with the CyTargetLinker app for Cytoscape
//
// Copyright 2011-2013 Department of Bioinformatics - BiGCaT, Maastricht University
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package cytargetlinker.conversion;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.bridgedb.DataSource;
import org.bridgedb.IDMapperStack;

import cytargetlinker.conversion.ArgsParser.AFilesAttributes;
import cytargetlinker.conversion.ArgsParser.AFilesIn;
import cytargetlinker.conversion.ArgsParser.AFilesOut;
import cytargetlinker.conversion.ArgsParser.AHelp;
import cytargetlinker.conversion.ArgsParser.GraphBuilder;
import cytargetlinker.conversion.data.GeneNode;
import cytargetlinker.conversion.data.INode;
import cytargetlinker.conversion.data.TFGeneInteraction;
import cytargetlinker.conversion.data.TFNode;
import cytargetlinker.conversion.graph.Graph;

/**
 * download the TF-gene networks from
 * http://encodenets.gersteinlab.org/enets2.Proximal_filtered.txt
 * http://encodenets.gersteinlab.org/enets3.Distal.txt
 * 
 * you can find the annotation file (GENCODE) here:
 * http://hgdownload.cse.ucsc.edu/goldenPath/hg19/encodeDCC/wgEncodeGencodeV12/wgEncodeGencodeAttrsV12.tab.gz
 * 
 * the family info can be found in a pdf (supplementary data)
 * for the annotation create a file with three columns: TF / TF Class / TF Family
 * 
 * @author martina
 *
 */
public class EncodeNetworks {

	private final static Logger log = Logger.getLogger(MiRecords.class.getName());
	private static Args pargs;
	private interface Args extends AHelp, AFilesIn, AFilesOut, AFilesAttributes {}
	
	/**
	 * USAGE: java -cp conversion.jar cytargetlinker.conversion.EncodeNetworks
	 * 
	 * REQUIRED ARGUMENTS:
	 * [-i] = input file path
	 * [-o] = output file path
	 * [-m] = list of bridgedb files for identifier mapping
	 * [-l] = log file path
	 * [--organism] = scientific name for the organism
	 * [-a] = annotation file (GENOCDE attributes)
	 * [-d] = description (distal or proximal)
	 *  
	 * OPTIONAL ARGUMENTS:
	 * [-f] = transcription family annotations
	 * [-n] = network name
	 * 
	 */
	public static void main(String argv[]) throws Exception {
		pargs = ArgsParser.parse(argv, Args.class);
		if(pargs.getAnnotationFile() != null) {
			EncodeNetworks converter = new EncodeNetworks();
			converter.startConversion();
		} else {
			log.severe("Please add annotation file to map from Ensembl transcript to Ensembl gene id.");
		}
	}
	
	private IDMapperStack idMapper;
	private Graph graph;
	
	private Map<String, String> annotationMap;
	private static Map<String, List<String>> annotationErrors;
	private Map<String, String> familyInfo;
	private List<String> sources;
	private List<String> targets;
	
	private Map<String, INode> nodes;
	private Map<String, TFGeneInteraction> interactions;
	
	private List<String> nodesNotFound;
	private List<String> interactionIgnored;

	public EncodeNetworks() throws Exception {
		annotationMap = new HashMap<String, String>();
		annotationErrors = new HashMap<String, List<String>>();
		familyInfo = new HashMap<String, String>();
		sources = new ArrayList<String>();
		targets = new ArrayList<String>();
		
		nodes = new HashMap<String, INode>();
		interactions = new HashMap<String, TFGeneInteraction>();
		nodesNotFound = new ArrayList<String>();
		interactionIgnored = new ArrayList<String>();
	}

	private void startConversion() throws Exception {
		File in = pargs.getInput();
		if(in != null) {
			if(pargs.getAnnotationFile() != null) {
				Utils.setUpLogger(log, getLogFile(), false);

				idMapper = Utils.initIDMapper(pargs, false);
				if(idMapper == null) {
					log.severe("no bridgedb file specified");
				} else {
					log.info("conversion of encode networks started ...\n");
		
					ArgsParser.convertAndWrite(pargs, pargs, new GraphBuilder() {
						public Graph buildGraph(File in) throws Exception {
							return convert(in);
						}
					});
						
					log.info("Encode networks file created.\n\n");
				}
			} else {
				log.severe("Please specify annotation file (GENCODE).");
			}
		} else {
			log.severe("Please specify input file!");
		}
	}
	
	
	
	private Graph convert(File in) {
		graph = new Graph();
		try {
			readAnnotations();
			if(pargs.isTFFamilyFile()) {
				readFamilyInfo(pargs.getTFFamilyFile());
			}
			
			setNetworkAttributes(in);
			
			try {
				BufferedReader br = new BufferedReader(new FileReader(in));
				String line;
				while((line = br.readLine()) != null) {
					String [] buffer = line.split("\t");
					if(buffer.length != 3) {
						buffer = line.split(" ");
					}
					String source = buffer[0];
					String target = buffer[2];
						
					if(!sources.contains(source)) {
						sources.add(source);
						if(targets.contains(source)) {
							targets.remove(source);
						}
					}
					if(!targets.contains(target)) {
						if(!sources.contains(target)) {
							targets.add(target);
						}
					}
				}
				br.close();
			} catch (IOException e) {
				log.warning("Could not read input file " + in.getAbsolutePath());
			}
			try {		
				BufferedReader br = new BufferedReader(new FileReader(in));
				String line;
				while((line = br.readLine()) != null) {
					String [] buffer = line.split("\t");
					if(buffer.length != 3) {
						buffer = line.split(" ");
					}
					String source = buffer[0];
					String target = buffer[2];
						
					if(annotationMap.containsKey(source) && annotationMap.containsKey(target)) {
						
						String tf = createNode(annotationMap.get(source), source, "transcriptionFactor");
						String gene;
						if(sources.contains(target)) {
							gene = createNode(annotationMap.get(target), target, "transcriptionFactor");
						} else {
							gene = createNode(annotationMap.get(target), target, "gene");
						}
						if(tf != null && gene != null) {
							if(!interactions.containsKey(tf + "_" + gene)) {
								
								TFGeneInteraction i = new TFGeneInteraction(nodes.get(tf), nodes.get(gene), "", "", "");
								interactions.put(tf + "_" + gene, i);
							}
						} else {
							if(!interactionIgnored.contains(source + " -> " + target)) {
								interactionIgnored.add(source + " -> " + target);
							}
						}
					} else {
						if(annotationMap.containsKey(source)) nodesNotFound.add(target);
						else nodesNotFound.add(source);
					}
				}
					
				br.close();
			} catch (IOException e) {
				log.warning("Could not read input file " + in.getAbsolutePath());
			}
			
			int count = 0;
			for(String str : interactions.keySet()) {
				TFGeneInteraction i = interactions.get(str);
				i.createEdge(graph, "ENCODE " + pargs.getDescription() + ")", count);
				count++;
			}

			int genes = 0;
			int tfs = 0;
			for(String str : nodes.keySet()) {
				if(nodes.get(str) instanceof GeneNode) {
					genes++;
				} else {
					tfs++;
				}
			}
			
			log.info(interactions.size() + " interactions have been found.\n" + genes + " gene nodes.\n" + tfs + " miRNA nodes.\n");
			log.info(interactionIgnored.size() + " interactions were ignores because " + nodesNotFound.size() + " nodes could not be mapped\n");
			
			cleanUp();
		} catch (IOException e) {
			log.severe("Could not parse annotation file.");
		}
		return graph;
	}

	private String createNode(String id, String name, String type) {
		if(type.equals("transcriptionFactor")) {
			String family = familyInfo.get(name);
			TFNode node = TFNode.createGeneNode(id, name, idMapper, DataSource.getBySystemCode("En"), family);
			if(node != null) {
				if(!nodes.containsKey(node.getId())) {
					nodes.put(node.getId(), node);
				}
				return node.getId();
			} else {
				if(!nodesNotFound.contains(id)) {
					nodesNotFound.add(id);
				}
				return null;
			}
		} else {
			GeneNode node = GeneNode.createGeneNode(id, name, idMapper, DataSource.getBySystemCode("En"));
			if(node != null) {
				if(!nodes.containsKey(node.getId())) {
					nodes.put(node.getId(), node);
				}
				return node.getId();
			} else {
				if(!nodesNotFound.contains(id)) {
					nodesNotFound.add(id);
				}
				return null;
			}
		}
	}
	
	private void setNetworkAttributes(File in) {
		graph.setTitle("ENCODE network (" + pargs.getDescription() + ")");
		graph.setAttribute(CommonAttributes.DATABASE.getName(), "ENCODE network (" + pargs.getDescription() + ")");
	
		graph.setAttribute(CommonAttributes.TYPE.getName(), "TF-gene interactions");
		graph.setAttribute(CommonAttributes.SOURCE_FILE.getName(), in.getName());
		graph.setAttribute(CommonAttributes.IDENTIFIERS.getName(), "identifiers");
		graph.setAttribute(CommonAttributes.TARGET_DATASOURCE.getName(), "Ensembl");
		graph.setAttribute(CommonAttributes.SOURCE_DATASOURCE.getName(), "Ensembl");
		graph.setAttribute(CommonAttributes.SOURCE_TYPE.getName(), "transcriptionFactor");
		graph.setAttribute(CommonAttributes.TARGET_TYPE.getName(), "gene");
	}

	private void readAnnotations() throws IOException {
		File file = new File(pargs.getAnnotationFile());
		BufferedReader reader = new BufferedReader(new FileReader(file));
		
		// read header line
		reader.readLine();
		
		String line;
		while((line = reader.readLine()) != null) {
			String [] str = line.split("\t");
			String name = str[1];
			String id = str[0];
			
			if(id.contains(".")) {
				int index = id.indexOf(".");
				id = id.substring(0,index);
			}
			
			if(annotationMap.containsKey(name)) {
				if(!annotationMap.get(name).equals(id)) {
					if(annotationErrors.containsKey(name)) {
						annotationErrors.get(name).add(id);
					} else {
						List<String> l = new ArrayList<String>();
						l.add(id);
						annotationErrors.put(name, l);
					}
				}
			} else {
				annotationMap.put(name, id);
			}
		}
		
		// remove gene names with more than one id
		for(String key : annotationErrors.keySet()) {
			annotationMap.remove(key);
		}
		
		reader.close();
	}
	
	private void readFamilyInfo(File file) throws IOException {
		BufferedReader reader = new BufferedReader(new FileReader(file));
		reader.readLine();
		String line;
		while((line = reader.readLine()) != null) {
			String [] str = line.split("\t");
			String name = str[0];
			String tfFamily = str[2];
			
			if(!familyInfo.containsKey(name)) {
				familyInfo.put(name, tfFamily);
			}
		}
	}
	
	private File getLogFile() {
		if(pargs.isLogFile()) {
			return pargs.getLogFile();
		} else {
			if(pargs.isInput()) {
				return new File(pargs.getInput() + ".log");
			} else {
				return null;
			}
		}
	}
	
	private void cleanUp() {
		nodes.clear();
		interactions.clear();
		nodesNotFound.clear();
		interactionIgnored.clear();
	}
}